#pragma once
#include <cereal/archives/binary.hpp>
#include <cereal/archives/json.hpp>
#include <cereal/types/vector.hpp>// to support T = std::vector
#include <cereal/types/string.hpp>// to support T = std::string
#include <fstream>

class CSerialization {
public:
	/** helpful template: Check if T has a serialize method */
	template<typename T, typename = void>
	struct has_serialize : std::false_type {};
	template<typename T>
	struct has_serialize<T, std::void_t<decltype(std::declval<T>().serialize(std::declval<cereal::JSONOutputArchive&>()))>> : std::true_type {};// C++17 support

	/**@warning you should match the type: `T& t` not `const T& t` */
	template<typename T>
	static bool serialize(T& t, const std::string& savePath, const std::string& jArrName = "", bool readable = true) {// readable just for debug
		bool success = false;
		try {
			if (readable) {
				std::ofstream ofs(savePath);// no need for binary mode
				if (ofs) {
					cereal::JSONOutputArchive archive(ofs);
					if constexpr (has_serialize<T>::value)
						t.serialize(archive);
					else {
						if (jArrName.empty())
							archive(t);
						else
							archive(cereal::make_nvp(jArrName, t));
					}
					success = true;
				}
				ofs.close();
			} else {
				std::ofstream ofs(savePath, std::ios::binary);
				if (ofs) {
					cereal::BinaryOutputArchive archive(ofs);
					archive(t);
					success = true;
				}
				ofs.close();
			}
		} catch (cereal::Exception e) {}
		return success;
	}

	template<typename T>
	static bool deserialize(T& t, const std::string& localPath, const std::string& jArrName = "", bool readable = true) {// readable just for debug
		bool success = false;
		std::ifstream ifs(localPath, std::ios::binary);
		if (ifs) {
			try {
				if (readable) {
					cereal::JSONInputArchive archive(ifs);
					if constexpr (has_serialize<T>::value)
						t.serialize(archive);
					else {
						if (jArrName.empty())
							archive(t);
						else
							archive(cereal::make_nvp(jArrName, t));
					}
				} else {
					cereal::BinaryInputArchive archive(ifs);
					archive(t);
				}
				success = true;
			} catch (cereal::Exception e) {}
		}
		ifs.close();
		return success;
	}
};

/****************** Usage Example (can run directly)***************************
#include "CSerialization.h"
#include <sstream>
#include <iostream>
const std::string LocalFilePath = "C:/Users/XXX/Desktop/person.json";

struct Person {
	std::string name;
	int age;
	bool isMale;
	float rate;
	std::vector<int> scores;

	Person() = default;

	Person(const std::string& _name, int _age, bool _isMale, float _rate, const std::vector<int>& _scores) {
		name = _name;
		age = _age;
		isMale = _isMale;
		rate = _rate;
		scores = _scores;
	}

	template<class Archive>
	void serialize(Archive& archive) {
		archive(CEREAL_NVP(name), CEREAL_NVP(age), CEREAL_NVP(isMale), CEREAL_NVP(rate), CEREAL_NVP(scores));
	}

	void print() {
		std::stringstream ss;
		for (auto score : scores) ss << score << ",";
		std::string scoresStr = ss.str();
		if (!scoresStr.empty()) scoresStr = scoresStr.substr(0, scoresStr.length() - 1);

		std::cout << "{\"name\":\"" << name << "\", \"age\":" << age << ", \"isMale\":" << std::boolalpha << isMale
				  << ", \"rate\":" << rate << ", \"scores\":[" << scoresStr << "]}" << std::endl;
	}
};

int main() {
	Person wPerson("akuan", 35, true, 3.14, {11, 22, 33});
	CSerialization::serialize(wPerson, LocalFilePath);

	Person rPerson;
	CSerialization::deserialize(rPerson, LocalFilePath);
	rPerson.print();

	return 0;
}
run the example, it will output:
{"name":"akuan", "age":35, "isMale":true, "rate":3.14, "scores":[11,22,33]}
*********************************************/